/*
 * Copyright 2013 Sigurd Randoll <srandoll@digiway.de>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.digiway.rapidbreeze.server.infrastructure.rest.download;

import de.digiway.rapidbreeze.shared.rest.download.DownloadResource;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.core.ClassNamesResourceConfig;
import com.sun.jersey.spi.container.servlet.WebComponent;
import com.sun.jersey.spi.inject.SingletonTypeInjectableProvider;
import com.sun.jersey.test.framework.AppDescriptor;
import com.sun.jersey.test.framework.JerseyTest;
import com.sun.jersey.test.framework.WebAppDescriptor;
import de.digiway.rapidbreeze.server.TestUtil;
import de.digiway.rapidbreeze.server.infrastructure.objectstorage.ObjectStorage;
import de.digiway.rapidbreeze.server.infrastructure.persistence.StorageProviderRepositoryStorage;
import de.digiway.rapidbreeze.server.infrastructure.persistence.TestProvider;
import de.digiway.rapidbreeze.server.model.download.Download;
import de.digiway.rapidbreeze.server.model.download.DownloadFactory;
import de.digiway.rapidbreeze.server.model.download.DownloadManager;
import de.digiway.rapidbreeze.server.model.storage.FileStatus;
import de.digiway.rapidbreeze.shared.rest.download.DownloadStatus;
import de.digiway.rapidbreeze.server.model.storage.StorageProviderRepository;
import de.digiway.rapidbreeze.server.model.storage.UrlStatus;
import de.digiway.rapidbreeze.shared.rest.ResourceLocation;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import org.junit.After;
import org.junit.Test;
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
import org.junit.Before;

/**
 *
 * @author Sigurd Randoll <srandoll@digiway.de>
 */
public class DownloadManagerResourceIT extends JerseyTest {

    private static DownloadManager downloadManager = mock(DownloadManager.class);
    private StorageProviderRepository storageProviderRepository;
    private Path storagePath;

    @Override
    protected AppDescriptor configure() {
        return new WebAppDescriptor.Builder().
                initParam(WebComponent.RESOURCE_CONFIG_CLASS, ClassNamesResourceConfig.class.getName()).
                initParam(ClassNamesResourceConfig.PROPERTY_CLASSNAMES, DownloadManagerResource.class.getName() + ";" + DownloadResource.class.getName() + ";" + MockDownloadManagerProvider.class.getName()).
                build();
    }

    @Before
    public void setup() {
        storagePath = TestUtil.getNonExistingTemporaryPath();
        ObjectStorage objectStorage = new ObjectStorage(storagePath);

        storageProviderRepository = new StorageProviderRepositoryStorage(objectStorage);
        DownloadFactory.registerStorageProviderRepository(storageProviderRepository);
    }

    @After
    public void tearDownDownloadManagerResourceIT() throws IOException {
        TestUtil.deleteDirectoryRecursive(storagePath);
    }

    @Test
    public void testGetDownloads() throws MalformedURLException, IOException {
        final URL url = new URL("http://" + TestProvider.PROVIDER_HOST);

        storageProviderRepository.registerProvider("Test", TestProvider.class);
        Download download1 = DownloadFactory.createDownload(url, Files.createTempFile("rapidbreeze", ".tmp"));
        Download download2 = DownloadFactory.createDownload(url, Files.createTempFile("rapidbreeze", ".tmp"));

        List<Download> downloads = new ArrayList<>();
        downloads.add(download1);
        downloads.add(download2);

        when(downloadManager.getDownloads()).thenReturn(downloads);

        WebResource webResource = resource();
        GenericType<List<DownloadResource>> genericType = new GenericType<List<DownloadResource>>() {
        };
        Collection<DownloadResource> get = webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).type(MediaType.APPLICATION_XML).get(genericType);
        assertEquals(2, get.size());

        // First resource:
        Iterator<DownloadResource> it = get.iterator();
        DownloadResource res1 = it.next();
        assertTrue(res1.isWaiting());
        assertEquals(download1.getFile().toString(), res1.getTargetFile());
        assertEquals(download1.getUrl(), res1.getUrl());

        // 2nd resource:
        DownloadResource res2 = it.next();
        assertTrue(res2.isWaiting());
        assertEquals(download2.getFile().toString(), res2.getTargetFile());
        assertEquals(download2.getUrl(), res2.getUrl());
    }

    @Test
    public void testGetDownload() throws Exception {
        final URL url = new URL("http://" + TestProvider.PROVIDER_HOST);

        storageProviderRepository.registerProvider("Test", TestProvider.class);
        Download download1 = mock(Download.class);
        when(download1.getIdentifier()).thenReturn("1");
        when(download1.getDownloadStatus()).thenReturn(DownloadStatus.WAITING);
        when(download1.getFile()).thenReturn(Files.createTempFile("rapidbreeze", ".tmp"));
        when(download1.getUrl()).thenReturn(url);

        UrlStatus status = mock(UrlStatus.class);
        when(status.getFileSize()).thenReturn(100L);
        when(status.getFileStatus()).thenReturn(FileStatus.OK);
        when(status.getFilename()).thenReturn("filename");
        when(status.getMd5()).thenReturn(null);
        when(download1.getUrlStatus()).thenReturn(status);

        when(downloadManager.hasDownload(download1.getIdentifier())).thenReturn(Boolean.TRUE);
        when(downloadManager.getDownload(download1.getIdentifier())).thenReturn(download1);

        // Fetch entry #1
        WebResource webResource = resource();
        DownloadResource resource = webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).path("1").type(MediaType.APPLICATION_XML).get(DownloadResource.class);

        assertEquals(download1.getFile().toString(), resource.getTargetFile());
        assertEquals(download1.getUrl(), resource.getUrl());
        assertTrue(resource.isWaiting());

        // try to fetch non-existing but with a longer:
        webResource = resource();
        ClientResponse response = webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).path("2").type(MediaType.APPLICATION_XML).head();
        assertEquals(ClientResponse.Status.NOT_FOUND.getStatusCode(), response.getStatus());

        // try to fetch non-existing with string:
        webResource = resource();
        response = webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).path("blaa").type(MediaType.APPLICATION_XML).head();
        assertEquals(ClientResponse.Status.NOT_FOUND.getStatusCode(), response.getStatus());

    }

    @Test
    public void testPostDownload() throws Exception {
        final URL url = new URL("http://" + TestProvider.PROVIDER_HOST);
        storageProviderRepository.registerProvider("Test", TestProvider.class);

        Download download1 = mock(Download.class);
        when(download1.getIdentifier()).thenReturn("1");
        when(download1.getDownloadStatus()).thenReturn(DownloadStatus.WAITING);
        when(download1.getFile()).thenReturn(Files.createTempFile("rapidbreeze", ".tmp"));
        when(download1.getUrl()).thenReturn(url);

        UrlStatus status = mock(UrlStatus.class);
        when(status.getFileSize()).thenReturn(100L);
        when(status.getFileStatus()).thenReturn(FileStatus.OK);
        when(status.getFilename()).thenReturn("filename");
        when(status.getMd5()).thenReturn(null);
        when(download1.getUrlStatus()).thenReturn(status);

        when(downloadManager.canHandleUrl(url)).thenReturn(Boolean.TRUE);
        when(downloadManager.addUrl(url)).thenReturn(download1);

        // Create new download:
        WebResource webResource = resource();
        ClientResponse response = webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).type(MediaType.APPLICATION_XML).post(ClientResponse.class, url.toString());
        assertEquals(ClientResponse.Status.CREATED.getStatusCode(), response.getStatus());
        assertEquals(webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).path(String.valueOf(download1.getIdentifier())).getURI(), response.getLocation());
        DownloadResource downloadResource = response.getEntity(DownloadResource.class);
        assertEquals(download1.getFile().toString(), downloadResource.getTargetFile());
        assertEquals(download1.getUrl(), downloadResource.getUrl());
        assertTrue(downloadResource.isWaiting());

        // Try to create new download with a bad URL:
        response = webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).type(MediaType.APPLICATION_XML).post(ClientResponse.class, "blubb");
        assertEquals(ClientResponse.Status.BAD_REQUEST.getStatusCode(), response.getStatus());

        // Try to create new download with a non-handleable URL:
        response = webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).type(MediaType.APPLICATION_XML).post(ClientResponse.class, "http://www.google.de/test.html");
        assertEquals(ClientResponse.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
    }

    @Test
    public void testPutDownload() throws MalformedURLException, IOException {
        final URL url = new URL("http://" + TestProvider.PROVIDER_HOST);

        storageProviderRepository.registerProvider("Test", TestProvider.class);

        Download download1 = mock(Download.class);
        when(download1.getIdentifier()).thenReturn("1");
        when(download1.getDownloadStatus()).thenReturn(DownloadStatus.WAITING);
        when(download1.getFile()).thenReturn(Files.createTempFile("rapidbreeze", ".tmp"));
        when(download1.getUrl()).thenReturn(url);

        UrlStatus status = mock(UrlStatus.class);
        when(status.getFileSize()).thenReturn(100L);
        when(status.getFileStatus()).thenReturn(FileStatus.OK);
        when(status.getFilename()).thenReturn("filename");
        when(status.getMd5()).thenReturn(null);
        when(download1.getUrlStatus()).thenReturn(status);

        List<Download> downloads = new ArrayList<>();
        downloads.add(download1);

        when(downloadManager.getDownloads()).thenReturn(downloads);
        when(downloadManager.getDownload(download1.getIdentifier())).thenReturn(download1);
        when(downloadManager.hasDownload(download1.getIdentifier())).thenReturn(Boolean.TRUE);

        WebResource webResource = resource();
        DownloadResource resource = webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).path("1").type(MediaType.APPLICATION_XML).get(DownloadResource.class);
        assertTrue(resource.isWaiting());

        // Start resource:
        resource.start();
        ClientResponse response = webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).path("1").type(MediaType.APPLICATION_XML).put(ClientResponse.class, resource);
        assertEquals(Status.OK, response.getClientResponseStatus());
        verify(downloadManager).startDownload(download1);
        when(download1.getDownloadStatus()).thenReturn(DownloadStatus.RUNNING);

        // Pause resource:
        resource.pause();
        response = webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).path("1").type(MediaType.APPLICATION_XML).put(ClientResponse.class, resource);
        assertEquals(Status.OK, response.getClientResponseStatus());
        verify(downloadManager).pauseDownload(download1);
        when(download1.getDownloadStatus()).thenReturn(DownloadStatus.PAUSE);

        // Unpause resource:
        resource.waitState();
        response = webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).path("1").type(MediaType.APPLICATION_XML).put(ClientResponse.class, resource);
        assertEquals(Status.OK, response.getClientResponseStatus());
        verify(downloadManager).waitDownload(download1);
        when(download1.getDownloadStatus()).thenReturn(DownloadStatus.WAITING);
    }

    @Test
    public void testDeleteDownload() throws Exception {
        final URL url = new URL("http://" + TestProvider.PROVIDER_HOST);

        storageProviderRepository.registerProvider("Test", TestProvider.class);

        Download download1 = mock(Download.class);
        when(download1.getIdentifier()).thenReturn("1");
        when(download1.getDownloadStatus()).thenReturn(DownloadStatus.WAITING);
        when(download1.getFile()).thenReturn(Files.createTempFile("rapidbreeze", ".tmp"));
        when(download1.getUrl()).thenReturn(url);

        UrlStatus status = mock(UrlStatus.class);
        when(status.getFileSize()).thenReturn(100L);
        when(status.getFileStatus()).thenReturn(FileStatus.OK);
        when(status.getFilename()).thenReturn("filename");
        when(status.getMd5()).thenReturn(null);
        when(download1.getUrlStatus()).thenReturn(status);

        List<Download> downloads = new ArrayList<>();
        downloads.add(download1);

        when(downloadManager.getDownloads()).thenReturn(downloads);
        when(downloadManager.getDownload(download1.getIdentifier())).thenReturn(download1);
        when(downloadManager.hasDownload(download1.getIdentifier())).thenReturn(Boolean.TRUE);

        WebResource webResource = resource();

        // OK Case:
        ClientResponse response = webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).path("1").type(MediaType.APPLICATION_XML).delete(ClientResponse.class);
        assertEquals(Status.OK.getStatusCode(), response.getStatus());
        verify(downloadManager).removeDownload(download1);

        // Not found
        response = webResource.path(ResourceLocation.DOWNLOAD_RESOURCE_PATH).path("2").type(MediaType.APPLICATION_XML).delete(ClientResponse.class);
        assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
    }

    public static class MockDownloadManagerProvider extends SingletonTypeInjectableProvider<Context, DownloadManager> {

        public MockDownloadManagerProvider() {
            super(DownloadManager.class, downloadManager);
        }
    }
}
